package me.zhengjie.recitation.util;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class FileUploadUtils {

    private static final int CHUNK_SIZE = 5 * 1024 * 1024; // 5MB
    private static final int BUFFER_SIZE = 16 * 1024;

    /**
     * 是否使用远程服务器
     */
    @Value("${config.use-remote-service}")
    private boolean useRemoteService;

    /**
     * 本地存储根路径
     */
    @Value("${local.base-path:/data/uploads-local}")
    private String localBasePath;

    @Value("${remote.base-path}")
    private String basePath;

    @Value("${remote.url-prefix}")
    private String urlPrefix;

    @Value("${upload.use-chunks:false}")
    private boolean useChunks;

    @Value("${upload.thread-pool-size:4}")
    private int poolSize;

    @Value("${cloud.aws.s3.bucket}")
    private String bucketName;

    @Autowired
    private S3Client s3Client;

    @Autowired
    private GenericObjectPool<ChannelSftp> sftpPool;
    /**
     * 上传文件到 Cloudflare R2，并返回访问 URL
     *
     * @param file     待上传的 Multipart 文件
     * @param userId   用户ID
     * @param poemId   诗词ID
     * @return FileMeta 包含 objectKey 与访问 URL
     */
    public FileMeta uploadToR2(MultipartFile file, Integer userId, Integer poemId) {
        Objects.requireNonNull(userId, "用户ID不能为空");
        Objects.requireNonNull(poemId, "诗词ID不能为空");
        validateFile(file);

        // 1. 生成安全文件名
        String safeName = UUID.randomUUID() + "_" +
                file.getOriginalFilename().replaceAll("[^a-zA-Z0-9.\\-]", "_");

        // 2. 构建 R2 对象键（Key）
        //    示例：audio/poem_123/user_456/uuid_filename.mp3
        String objectKey = String.format("audio/poem_%d/user_%d/%s",
                poemId, userId, safeName);
        if (!useRemoteService) {
            // 本地存储
            try {
                Path dir = Paths.get(localBasePath, "audio/poem_" + poemId, "user_" + userId);
                Files.createDirectories(dir);
                Path target = dir.resolve(safeName);
                try (InputStream in = file.getInputStream()) {
                    Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
                }
                String absPath = target.toString();
                // 提取相对 URL 路径: 去掉本地根路径并统一为 '/audio/poem_xxx/...'
                String relative = absPath.replace("\\", "/");
                String urlPath = relative.startsWith(localBasePath.replace("\\", "/"))
                        ? relative.substring(localBasePath.replace("\\", "/").length())
                        : relative;
                if (!urlPath.startsWith("/")) {
                    urlPath = "/" + urlPath;
                }
                return new FileMeta(absPath, urlPath);
            } catch (IOException e) {
                throw new RuntimeException("本地存储失败: " + e.getMessage(), e);
            }
        }

        try {
            // 3. 构建并发起 PutObject 请求 :contentReference[oaicite:1]{index=1}
            PutObjectRequest request = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(objectKey)
                    .contentType(file.getContentType())
                    .build();

            s3Client.putObject(request,
                    RequestBody.fromInputStream(file.getInputStream(), file.getSize()));

            // 4. 返回包含 remotePath（objectKey）与 accessUrl
            String accessUrl = buildAccessUrl(objectKey);
            return new FileMeta(objectKey, accessUrl);

        } catch (IOException e) {
            throw new RuntimeException("读取文件流失败: " + e.getMessage(), e);
        } catch (S3Exception e) {
            throw new RuntimeException("上传至 R2 失败: " +
                    e.awsErrorDetails().errorMessage(), e);
        }
    }
    /**
     * 覆盖上传：直接用传入的 oldObjectKey 作为目标 Key。
     */
    public FileMeta overwriteToR2(MultipartFile file,
                                  String oldObjectKey) {
        Objects.requireNonNull(oldObjectKey, "原 objectKey 不能为空");
        validateFile(file);

        if (!useRemoteService) {
            // 本地覆盖
            try {
                Path target = Paths.get(oldObjectKey);
                Files.createDirectories(target.getParent());
                try (InputStream in = file.getInputStream()) {
                    Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
                }
                String accessUrl = target.toUri().toString();
                return new FileMeta(target.toString(), accessUrl);
            } catch (IOException e) {
                throw new RuntimeException("本地覆盖上传失败: " + e.getMessage(), e);
            }
        }

        try {
            PutObjectRequest req = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(oldObjectKey)
                    .contentType(file.getContentType())
                    .build();

            s3Client.putObject(req,
                    RequestBody.fromInputStream(file.getInputStream(), file.getSize()));

            String url = buildAccessUrl(oldObjectKey);
            return new FileMeta(oldObjectKey, url);
        } catch (IOException | S3Exception e) {
            throw new RuntimeException("覆盖上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传入口：根据配置和文件大小，选择直传或分片并行
     */
    public FileMeta upload(MultipartFile file, Integer userId, Integer poemId) {
        Objects.requireNonNull(userId, "用户ID不能为空");
        Objects.requireNonNull(poemId, "诗词ID不能为空");
        validateFile(file);

        // 1. 生成安全文件名及远程路径
        String safeName = UUID.randomUUID() + "_" + sanitize(file.getOriginalFilename());
        String remotePath = Paths.get(basePath, "audio", "poem_" + poemId, "user_" + userId, safeName)
                .toString().replace("\\", "/");

        // 2. 写本地临时文件
        File tempFile;
        try {
            tempFile = File.createTempFile("upload_", null);
            file.transferTo(tempFile);
        } catch (IOException e) {
            throw new RuntimeException("写入临时文件失败", e);
        }

        // 3. 上传（直传或分片）
        try {
            if (useChunks && tempFile.length() > CHUNK_SIZE) {
                parallelChunkUpload(tempFile, remotePath);
            } else {
                singleFileUpload(tempFile.getAbsolutePath(), remotePath);
            }
        } catch (Exception ex) {
            throw new RuntimeException("上传失败: " + ex.getMessage(), ex);
        } finally {
            tempFile.delete();
        }

        // 4. 返回元数据
        return new FileMeta(remotePath, buildAccessUrl(remotePath));
    }

    /** 单文件直传 **/
    private void singleFileUpload(String localPath, String remotePath) {
        ChannelSftp channel = null;
        try {
            channel = sftpPool.borrowObject();
            createRemoteDirectories(channel, remotePath);
            channel.put(localPath, remotePath, null, ChannelSftp.OVERWRITE);
        } catch (Exception e) {
            invalidate(channel);
            throw new RuntimeException("直传失败", e);
        } finally {
            returnQuietly(channel);
        }
    }

    /** 并行分片上传 **/
    private void parallelChunkUpload(File localFile, String remotePath) throws Exception {
        long totalSize = localFile.length();
        int chunks = (int) Math.ceil((double) totalSize / CHUNK_SIZE);
        ExecutorService executor = Executors.newFixedThreadPool(poolSize);
        CountDownLatch latch = new CountDownLatch(chunks);
        AtomicInteger errors = new AtomicInteger(0);

        for (int i = 0; i < chunks; i++) {
            final int idx = i;
            executor.submit(() -> {
                ChannelSftp channel = null;
                try (RandomAccessFile raf = new RandomAccessFile(localFile, "r")) {
                    raf.seek((long) idx * CHUNK_SIZE);
                    long len = Math.min(CHUNK_SIZE, totalSize - idx * CHUNK_SIZE);
                    InputStream partStream = new BufferedInputStream(
                            new FileInputStream(raf.getFD()), BUFFER_SIZE);

                    channel = sftpPool.borrowObject();
                    createRemoteDirectories(channel, remotePath);
                    channel.put(partStream, remotePath + ".part" + idx, null, ChannelSftp.OVERWRITE);
                } catch (Exception e) {
                    errors.incrementAndGet();
                } finally {
                    returnQuietly(channel);
                    latch.countDown();
                }
            });
        }

        latch.await();
        executor.shutdown();
        if (errors.get() > 0) {
            throw new RuntimeException("分片上传失败次数：" + errors.get());
        }
        // 合并
        mergeChunks(remotePath, chunks);
    }

    /** 远程服务器合并分片并删除 **/
    private void mergeChunks(String remotePath, int chunks) throws Exception {
        ChannelSftp channel = sftpPool.borrowObject();
        try {
            ChannelExec exec = (ChannelExec) channel.getSession().openChannel("exec");
            StringBuilder cmd = new StringBuilder("cat ");
            for (int i = 0; i < chunks; i++) {
                cmd.append(remotePath).append(".part").append(i).append(" ");
            }
            cmd.append("> ").append(remotePath).append(" && rm ").append(remotePath).append(".part*");
            exec.setCommand(cmd.toString());
            exec.connect();
            while (!exec.isClosed()) {
                Thread.sleep(100);
            }
            exec.disconnect();
        } finally {
            returnQuietly(channel);
        }
    }

    /** 生成访问 URL：去掉 basePath 前缀 **/
    private String buildAccessUrl(String remotePath) {
        String clean = remotePath;
        if (remotePath.startsWith(basePath)) {
            clean = remotePath.substring(basePath.length());
        }
        return UriComponentsBuilder.fromUriString(urlPrefix)
                .path(clean)
                .toUriString();
    }

    /** 递归创建远程目录 **/
    private void createRemoteDirectories(ChannelSftp ch, String remotePath) throws SftpException {
        String dir = parentDir(remotePath);
        String[] parts = dir.split("/");
        StringBuilder path = new StringBuilder();
        for (String p : parts) {
            if (p.isEmpty()) continue;
            path.append('/').append(p);
            String cur = path.toString();
            try { ch.stat(cur); }
            catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) ch.mkdir(cur);
                else throw e;
            }
        }
    }

    /** 获取父目录 **/
    private String parentDir(String p) {
        int idx = p.lastIndexOf('/');
        return idx > 0 ? p.substring(0, idx) : "/";
    }

    /** 验证扩展名 **/
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) throw new RuntimeException("文件不能为空");
        String n = file.getOriginalFilename();
        if (!StringUtils.hasText(n) || !n.contains(".")) throw new RuntimeException("文件名不合法");
        String ext = StringUtils.getFilenameExtension(n).toLowerCase();
        Set<String> allowed = Set.of("mp3","wav","flac","aac","m4a","ogg");
        if (!allowed.contains(ext)) throw new RuntimeException("仅支持格式: " + allowed);
    }

    /** 安全化文件名 **/
    private String sanitize(String name) {
        return StringUtils.hasText(name) ? name.replaceAll("[^a-zA-Z0-9.\\-]", "_") : "";
    }

    /** 失效并销毁通道 **/
    private void invalidate(ChannelSftp ch) {
        if (ch != null) try { sftpPool.invalidateObject(ch); } catch (Exception ignored) {}
    }

    /** 安静归还通道 **/
    private void returnQuietly(ChannelSftp ch) {
        if (ch != null) try { sftpPool.returnObject(ch); } catch (Exception ignored) {}
    }

}
